using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Http;
using System.IO;
using System.Net;
using System.Threading.Tasks;
using System.Threading;
using Newtonsoft.Json;
using System.Web;

namespace System.Net.Http
{
    using global::System.Net.Http;
    public class HttpShell : HttpClient, IDisposable
    {
        public string UserAgent { get; set; } = "Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1667.0 Safari/537.36";
        public HttpShell(bool usecookies = false) : this(new HttpClientHandler() { UseCookies = usecookies, PreAuthenticate = true, UseDefaultCredentials = true })
        {
        }
        public HttpShell(HttpMessageHandler msghandler, bool disposehandler = true) : base(msghandler, disposehandler)
        {
            if (!string.IsNullOrWhiteSpace(UserAgent))
            {
                this.DefaultRequestHeaders.Add("user-agent", UserAgent);
            }
            this.DefaultRequestHeaders.Add("X-Requested-With", "XMLHttpRequest");
        }
        /// <summary>
        /// 限定所使用的本地TCP端口（一般在需要穿透NAT设备的情况下使用）
        /// </summary>
        /// <param name="localport"></param>
        public HttpShell(int localport) : this(new ManagedHandler(localport, true))
        {

        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="serverroot">指定服务器根目录后，API相关的方法可以使用相对路径来访问。</param>
        /// <param name="usecookies"></param>
        public HttpShell(string serverroot, bool usecookies = false) : this(usecookies)
        {
            this.BaseAddress = new Uri(serverroot); 
        }
       
        public override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        { 

            return base.SendAsync(request, cancellationToken);
        }



        #region API

        //private Uri geturi(string path)
        //{
        //    return GetUriWithServerRoot(path);
        //}
        /// <summary>
        /// 将值转换为HttpContent
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        private HttpContent obj2HttpContent(object obj)
        {
            if (obj == null)
            {
                return new StringContent(null);
            }
            else if (obj is DateTime dt)
            {
                return new StringContent(dt.ToString());//("yyyy-MM-ddd HH:mm:ss")
            }
            else if (obj is Stream stream)
            {
                return new StreamContent(stream);
            }
            else if (obj is byte[] bs)
            {
                return new ByteArrayContent(bs);
            }
            else if (obj is HttpContent hc)
            {
                return hc;
            }
            else
            {
                return new StringContent(obj.ToString());
            }
        }
        /// <summary>
        /// 将对象转换为FormData
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        private MultipartFormDataContent obj2MultipartFormDataContent(object obj)
        {
            MultipartFormDataContent mc = new MultipartFormDataContent();
            if (obj != null)
            {
                var t = obj.GetType();
                var pros = t.GetProperties();
                foreach (var item in pros)
                {
                    var value = item.GetValue(obj);
                    var content = obj2HttpContent(value);
                    if (content is StreamContent)
                    {//流类型的数据添加文件名（服务器端以IFormFile接收）
                        mc.Add(content, item.Name, "tempfile");
                    }
                    else
                    {
                        mc.Add(content, item.Name);
                    }

                }

            }
            return mc;
        }
        /// <summary>
        /// 将对象转换为http查询字符串
        /// </summary>
        /// <param name="paras"></param>
        /// <returns></returns>
        private string getParasQuery(object paras)
        {
            if (paras == null)
            {
                return "";
            }
            List<string> kvs = new List<string>();
            foreach (var p in paras.GetType().GetProperties())
            {
                var value = p.GetValue(paras);
                //kvs.Add($"{HttpUtility.UrlEncode(p.Name)}={(value != null ? HttpUtility.UrlEncode(value.ToString()) : "")}");
                kvs.Add($"{p.Name}={value.ToString()}");
            }
            return string.Join("&", kvs);
        }


        public async Task EnsureSuccessStatusCode(HttpResponseMessage resp)
        {
            if (resp.StatusCode == HttpStatusCode.InternalServerError)
            {
                var content = await resp.Content.ReadAsStringAsync();
                throw new Exception($"服务器内部异常，{content}");
            }
            else
            {
                await Task.Run(delegate
                {
                    resp.EnsureSuccessStatusCode();
                }
                );

            }
        }

        public async Task<ApiResult> ApiDeleteAsync(string url )
        {
             
            var resp = await this.DeleteAsync(url);
            await EnsureSuccessStatusCode(resp);
            var apiresult = await resp.GetApiResult();
            return apiresult;
        }
        public async Task<ApiResult> ApiPutAsync(string url, HttpContent content)
        {
            // var uri = geturi(url);
            var resp = await this.PutAsync(url, content);
            await EnsureSuccessStatusCode(resp);
            var apiresult = await resp.GetApiResult();
            return apiresult;
        }

        public async Task<ApiResult> ApiGetAsync(string url )
        { 
            var resp = await this.GetAsync(url);
            await EnsureSuccessStatusCode(resp);
            var apiresult = await resp.GetApiResult();
            return apiresult;
        }
        public async Task<ApiResult> ApiPostAsync(string url, HttpContent content)
        { 
            var resp = await this.PostAsync(url, content);
             
            await EnsureSuccessStatusCode(resp);
            var apiresult = await resp.GetApiResult();
            return apiresult;
        }
        public async Task<ApiResult> ApiPostAsync(string url, PostContentType pct, object model)
        {
            HttpContent content;
            switch (pct)
            {
                case PostContentType.Json:
                    var txt = JsonConvert.SerializeObject(model);
                    content = new StringContent(txt, Encoding.UTF8, "application/json");
                    break;
                case PostContentType.MultipartFormData:
                    MultipartFormDataContent mfdc = obj2MultipartFormDataContent(model);
                    content = mfdc;
                    break;
                default:
                    content = null;
                    break;
            }
            var apiresult = await this.ApiPostAsync(url, content);
            return apiresult;
        }
        /// <summary>
        /// 下载文件
        /// </summary>
        /// <param name="url"></param>
        /// <param name="streamToWrite"></param>
        /// <param name="onprogress"></param>
        public async Task DownloadFile(string url, Stream streamToWrite, ProgressNotify onprogress = null, int bufsize = 1024 * 1024)
        {
            var resp = await this.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);//下载文件时仅先读取响应头以获取文件长度
            await EnsureSuccessStatusCode(resp);
            var cl = resp.Content.Headers.ContentLength;
            var stream = await resp.Content.ReadAsStreamAsync();
            byte[] buff = new byte[bufsize];
            int loaded = 0;
            int total = cl.HasValue ? (int)cl.Value : 0;

            while (true)
            {
                var length = stream.Read(buff, 0, buff.Length);
                if (length == 0)
                {
                    break;
                }
                streamToWrite.Write(buff, 0, length);
                loaded += length;
                onprogress?.Invoke(loaded, total);
            }

        }
        #endregion

        #region Post
        /// <summary>
        /// 提交多个文件
        /// </summary>
        /// <param name="requestUri"></param>
        /// <param name="files"></param>
        /// <returns></returns>
        public async Task<HttpResponseMessage> PostAsync(string requestUri, (string field, Stream stream, string filename)[] files)
        {
            MultipartFormDataContent mf = new MultipartFormDataContent();
            foreach (var item in files)
            {
                mf.Add(new StreamContent(item.stream), item.field, item.filename);
            }

            return await PostAsync(requestUri, mf);

        }
         

        /// <summary>
        /// 提交文本内容并指定其编码和内容类型
        /// </summary>
        /// <param name="requestUri"></param>
        /// <param name="content"></param>
        /// <param name="encoding"></param>
        /// <param name="contentType"></param>
        /// <returns></returns>
        public Task<HttpResponseMessage> PostAsync(string requestUri, string content, Encoding encoding = null, string contentType = null)
        { 
            var strc = new StringContent(content, encoding ?? Encoding.UTF8) { };
            strc.Headers.ContentType = new Headers.MediaTypeHeaderValue(contentType);
            return base.PostAsync(requestUri, strc);
        }
        /// <summary>
        /// 提交数据流并指定其缓存大小
        /// </summary>
        /// <param name="requestUri"></param>
        /// <param name="content"></param>
        /// <param name="buffersize"></param>
        /// <returns></returns>
        public Task<HttpResponseMessage> PostAsync(string requestUri, Stream content, int buffersize = 1024 * 5)
        { 
            return base.PostAsync(requestUri, new StreamContent(content, buffersize));
        }

         
        #endregion
    }
    /// <summary>
    /// 进度通知
    /// </summary>
    /// <param name="loaded">已加载的数量</param>
    /// <param name="total">总数量</param>
    public delegate void ProgressNotify(int loaded, int total);
}
